<head>
    <style>
        html, body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            display: block;
        }
        #canvas {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            text-align: center;
            position: fixed;
            position: relative;
        }
        
        .error {
            font-family: Consolas;
            font-size: 1.2em;
            color: black;
            box-sizing: border-box;
            background-color: lightcoral;
            border-radius: 2px;
            border-color: lightblue;
            border-width: thin;
            border-style: solid;
            line-height: 1.4em;
            cursor:pointer;
        }
        .error:hover {
            color: black;
            background-color: brown;
            border-color: blue;
        }
        #message {
            font-family: Consolas;
            font-size: 1.2em;
            color: #ccc;
            background-color: black;
            font-weight: bold;
            z-index: 2;
            position: absolute;
        }

        #dat_gui_container {
            position: absolute;
            left: 0px;   /* position inside relatively positioned parent */
            top: 0px;
            z-index: 3;   /* adjust as needed */
        }

        /* Pause Button Style */

        .rec_base {
            position: absolute;
            border: none;
            right: 0px;
            padding: 26px;
            text-align: center;
            text-decoration: none;
            font-size: 26px;
            border-radius: 8px;
            margin: 8px;
            transform: translateX(0%);
            z-index: 1;
        }
        .rec_base:nth-of-type(2) {
            top: 60px;
        }
        
        /* Screenshot Button Style */
        
        /* Record Button Style */

        /* Reload Button Style */
    </style>
</head>
<body>
    <div id="message"></div>
    <div id="dat_gui_container"></div>
    <div id="container">
        <!-- Pause Element -->
    </div>
    <!-- Screenshot Element -->
    <!-- Record Element -->
    <!-- Reload Element -->
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<!-- Stats.js -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js' onload="
let stats = new Stats();
compileTimePanel = stats.addPanel(new Stats.Panel('CT MS', '#ff8', '#221'));
stats.showPanel(1);
document.body.appendChild(stats.domElement);
requestAnimationFrame(function loop() {
    stats.update();
    requestAnimationFrame(loop);
});
"></script>
<!-- dat.gui -->

<canvas id="canvas"></canvas>

<!-- Shaders -->

<script id='d:/归档/threejs-learn/asset/demo/first-fragment.glsl' type='x-shader/x-fragment'>
uniform vec3        iResolution;
uniform float       iTime;
uniform float       iTimeDelta;
uniform int         iFrame;
uniform vec4        iDate;
uniform vec3        iChannelResolution[10];
uniform vec4        iMouse;
uniform vec4        iMouseButton;
uniform mat4        iViewMatrix;
uniform sampler2D   iChannel0;
uniform sampler2D   iChannel1;
uniform sampler2D   iChannel2;
uniform sampler2D   iChannel3;
uniform sampler2D   iChannel4;
uniform sampler2D   iChannel5;
uniform sampler2D   iChannel6;
uniform sampler2D   iChannel7;
uniform sampler2D   iChannel8;
uniform sampler2D   iChannel9;
uniform sampler2D   iKeyboard;
uniform float       iSampleRate;

#define iGlobalTime iTime
#define iGlobalFrame iFrame

#define SHADER_TOY


void mainImage( out vec4 fragColor, in vec2 fragCoord ){
    vec2 uv = (fragCoord.xy / iResolution.xy - 0.5) * 2.0;
    vec2 nuv = fract(uv*3.0);
    vec2 onePixel= vec2(1.0) / iResolution.xy;
    vec3 color = vec3(0.0);
    if(abs(uv.x) < fwidth(nuv.x)||abs(uv.y) < fwidth(nuv.y)){
      color = vec3(1.0);
    }
    fragColor = vec4(color,1.0);
}
void main() {
    vec2 fragCoord = gl_FragCoord.xy;
    mainImage(gl_FragColor, fragCoord);
}
</script>

<!-- FlyControls -->

<script type="text/javascript">
    let vscode = undefined;
    if (typeof acquireVsCodeApi === 'function') {
        vscode = acquireVsCodeApi();
    }
    var compileTimePanel;

    let revealError = function(line, file) {
        if (vscode) {
            vscode.postMessage({
                command: 'showGlslsError',
                line: line,
                file: file
            });
        }
    };

    let currentShader = {};
    // Error Callback
    console.error = function () {
        if('7' in arguments) {
            let errorRegex = /ERROR: \d+:(\d+):\W(.*)\n/g;
            let rawErrors = arguments[7];
            let match;
            
            let diagnostics = [];
            let message = '';
            while(match = errorRegex.exec(rawErrors)) {
                let lineNumber = Number(match[1]) - currentShader.LineOffset;
                let error = match[2];
                diagnostics.push({
                    line: lineNumber,
                    message: error
                });
                let lineHighlight = `<a class='error' unselectable onclick='revealError(${lineNumber}, "${currentShader.File}")'>Line ${lineNumber}</a>`;
                message += `<li>${lineHighlight}: ${error}</li>`;
            }
            console.log(message);
            let diagnosticBatch = {
                filename: currentShader.File,
                diagnostics: diagnostics
            };
            if (vscode !== undefined) {
                vscode.postMessage({
                    command: 'showGlslDiagnostic',
                    type: 'error',
                    diagnosticBatch: diagnosticBatch
                });
            }
    
            $('#message').append(`<h3>Shader failed to compile - ${currentShader.Name} </h3>`);
            $('#message').append('<ul>');
            $('#message').append(message);
            $('#message').append('</ul>');
        }
    };

    // Development feature: Output warnings from third-party libraries
    // console.warn = function (message) {
    //     $("#message").append(message + '<br>');
    // };

    let clock = new THREE.Clock();
    let pausedTime = 0.0;
    let deltaTime = 0.0;
    let startingTime = 1215.575599999955;
    let time = startingTime;

    let date = new THREE.Vector4();

    let updateDate = function() {
        let today = new Date();
        date.x = today.getFullYear();
        date.y = today.getMonth();
        date.z = today.getDate();
        date.w = today.getHours() * 60 * 60 
            + today.getMinutes() * 60
            + today.getSeconds()
            + today.getMilliseconds() * 0.001;
    };
    updateDate();

    let paused = false;
    let forceRenderOneFrame = paused;
    let pauseButton = document.getElementById('pause-button');
    if (pauseButton) {
        pauseButton.checked = paused;
        pauseButton.onclick = function(){
            paused = pauseButton.checked;
            if (!paused) {
                // Audio Resume
                pausedTime += clock.getDelta();
            }
            else {
                // Audio Pause
            }

            if (vscode !== undefined) {
                vscode.postMessage({
                    command: 'setPause',
                    paused: paused
                });
            }
        };
    }
    
    {
        let screenshotButton = document.getElementById("screenshot");
        if (screenshotButton) {
            screenshotButton.addEventListener('click', saveScreenshot);
        }
    }
    
    {
        let recordButton = document.getElementById("record");
        if (recordButton) {
            recordButton.addEventListener('click', recordAction);
        }
    }
    
    {
        let reloadButton = document.getElementById("reload");
        if (reloadButton) {
            reloadButton.addEventListener('click', reloadWebview);
        }
    }
    
    window.addEventListener('message', event => {
        const message = event.data; // The JSON data our extension sent
        switch (message.command) {
            case 'pause':
                if (pauseButton) {
                    pauseButton.checked = !pauseButton.checked;
                }
                paused = !paused;
                if (!paused) {
                    // Audio Resume
                    pausedTime += clock.getDelta();
                }
                else {
                    // Audio Pause
                }

                if (vscode !== undefined) {
                    vscode.postMessage({
                        command: 'setPause',
                        paused: paused
                    });
                }
                break;
            case 'screenshot':
                saveScreenshot();
                break;
        }
    });

    let canvas = document.getElementById('canvas');
    let gl = canvas.getContext('webgl2');
    let isWebGL2 = gl != null;
    if (gl == null) gl = canvas.getContext('webgl');
    let supportsFloatFramebuffer = (gl.getExtension('EXT_color_buffer_float') != null) || (gl.getExtension('WEBGL_color_buffer_float') != null);
    let supportsHalfFloatFramebuffer = (gl.getExtension('EXT_color_buffer_half_float') != null);
    let framebufferType = THREE.UnsignedByteType;
    if (supportsFloatFramebuffer) framebufferType = THREE.FloatType;
    else if (supportsHalfFloatFramebuffer) framebufferType = THREE.HalfFloatType;

    let renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, context: gl, preserveDrawingBuffer: true });
    let resolution = forceAspectRatio(window.innerWidth, window.innerHeight);
    let mouse = new THREE.Vector4(205, 305, -205, -305);
    let mouseButton = new THREE.Vector4(0, 0, 0, 0);
    let normalizedMouse = new THREE.Vector2(0.004155124653739612, 0.8363844393592678);
    let frameCounter = 0;
    let recorder = null;

    // Audio Init
    const audioContext = {
        sampleRate: 0
    };
    // Audio Resume

    let buffers = [];
    // Buffers
    buffers.push({
        Name: 'd:/归档/threejs-learn/asset/demo/first-fragment.glsl',
        File: 'd:/归档/threejs-learn/asset/demo/first-fragment.glsl',
        LineOffset: 134,
        Target: null,
        ChannelResolution: Array(10).fill(new THREE.Vector3(0,0,0)),
        PingPongTarget: null,
        PingPongChannel: 0,
        Dependents: [],
        Shader: new THREE.ShaderMaterial({
            fragmentShader: document.getElementById('d:/归档/threejs-learn/asset/demo/first-fragment.glsl').textContent,
            depthWrite: false,
            depthTest: false,
            uniforms: {
                iResolution: { type: 'v3', value: resolution },
                iTime: { type: 'f', value: 0.0 },
                iTimeDelta: { type: 'f', value: 0.0 },
                iFrame: { type: 'i', value: 0 },
                iMouse: { type: 'v4', value: mouse },
                iMouseButton: { type: 'v2', value: mouseButton },
                iViewMatrix: {type: 'm44', value: new THREE.Matrix4() },
                iChannelResolution: { type: 'v3v', value: Array(10).fill(new THREE.Vector3(0,0,0)) },
    
                iDate: { type: 'v4', value: date },
                iSampleRate: { type: 'f', value: audioContext.sampleRate },
    
                iChannel0: { type: 't' },
                iChannel1: { type: 't' },
                iChannel2: { type: 't' },
                iChannel3: { type: 't' },
                iChannel4: { type: 't' },
                iChannel5: { type: 't' },
                iChannel6: { type: 't' },
                iChannel7: { type: 't' },
                iChannel8: { type: 't' },
                iChannel9: { type: 't' },
    
                resolution: { type: 'v2', value: resolution },
                time: { type: 'f', value: 0.0 },
                mouse: { type: 'v2', value: normalizedMouse },
            }
        })
    });
    let commonIncludes = [];
    // Includes
    

    // WebGL2 inserts more lines into the shader
    if (isWebGL2) {
        for (let buffer of buffers) {
            buffer.LineOffset += 16;
        }
    }

    // Keyboard Init
    
    // Uniforms Init
    // Uniforms Update

    let texLoader = new THREE.TextureLoader();
    // Texture Init
    

    let scene = new THREE.Scene();
    let quad = new THREE.Mesh(
        new THREE.PlaneGeometry(resolution.x, resolution.y),
        null
    );
    scene.add(quad);

    let controlState = new THREE.Camera();
    controlState.position.set(0,0,0);
    controlState.quaternion.set(0,0,0,1);
    scene.add(controlState);

    let flyControls = undefined;
    if (typeof FlyControls === 'function') {
        flyControls = new FlyControls(controlState, renderer.domElement, vscode);
        flyControls.movementSpeed = 1;
        flyControls.domElement = renderer.domElement;
        flyControls.rollSpeed = Math.PI / 24;
        flyControls.autoForward = false;
        flyControls.dragToLook = true;
    }

    let camera = new THREE.OrthographicCamera(-resolution.x / 2.0, resolution.x / 2.0, resolution.y / 2.0, -resolution.y / 2.0, 1, 1000);
    camera.position.set(0, 0, 10);

    // Run every shader once to check for compile errors
    let compileTimeStart = performance.now();
    let failed=0;
    for (let include of commonIncludes) {
        currentShader = {
            Name: include.Name,
            File: include.File,
            // add two for version and precision lines
            LineOffset: 27 + 2
        };
        // Test Compile Included Files
        // bail if there is an error found in the include script
        if(compileFragShader(gl, document.getElementById(include.Name).textContent) == false) {
            throw Error(`Failed to compile ${include.Name}`);
        }
    }

    for (let buffer of buffers) {
        currentShader = {
            Name: buffer.Name,
            File: buffer.File,
            LineOffset: buffer.LineOffset
        };
        quad.material = buffer.Shader;
        renderer.setRenderTarget(buffer.Target);
        renderer.render(scene, camera);
    }
    currentShader = {};
    let compileTimeEnd = performance.now();
    let compileTime = compileTimeEnd - compileTimeStart;
    if (compileTimePanel !== undefined) {
        for (let i = 0; i < 200; i++) {
            compileTimePanel.update(compileTime, 200);
        }
    }

    computeSize();
    render();

    function addLineNumbers( string ) {
        let lines = string.split( '\\n' );
        for ( let i = 0; i < lines.length; i ++ ) {
            lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];
        }
        return lines.join( '\\n' );
    }

    function compileFragShader(gl, fsSource) {
        const fs = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fs, fsSource);
        gl.compileShader(fs);
        if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
            const fragmentLog = gl.getShaderInfoLog(fs);
            console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.COMPILE_STATUS', null, null, null, null, fragmentLog );
            return false;
        }
        return true;
    }

    function render() {
        requestAnimationFrame(render);
        if (!forceRenderOneFrame) {
            // Pause Whole Render
            if (paused) return;
        }
        forceRenderOneFrame = false;

        // Advance Time
        deltaTime = clock.getDelta();
        time = startingTime + clock.getElapsedTime() - pausedTime;
        if (vscode !== undefined) {
            vscode.postMessage({
                command: 'updateTime',
                time: time
            });
        }
        updateDate();
        
        if(flyControls)
        {
            flyControls.update(0.1);
        }

        // Audio Update

        for (let buffer of buffers) {
            buffer.Shader.uniforms['iResolution'].value = resolution;
            buffer.Shader.uniforms['iTimeDelta'].value = deltaTime;
            buffer.Shader.uniforms['iTime'].value = time;
            buffer.Shader.uniforms['iFrame'].value = frameCounter;
            buffer.Shader.uniforms['iMouse'].value = mouse;
            buffer.Shader.uniforms['iMouseButton'].value = mouseButton;

            buffer.Shader.uniforms['iViewMatrix'].value = controlState.matrixWorld;

            buffer.Shader.uniforms['resolution'].value = resolution;
            buffer.Shader.uniforms['time'].value = time;
            buffer.Shader.uniforms['mouse'].value = normalizedMouse;

            quad.material = buffer.Shader;
            renderer.setRenderTarget(buffer.Target);
            renderer.render(scene, camera);
        }
        
        // Uniforms Update

        // Keyboard Update

        if (mouse.w > 0.0) {
            mouse.w = -mouse.w;
            updateMouse();
        }

        for (let buffer of buffers) {
            if (buffer.PingPongTarget) {
                [buffer.PingPongTarget, buffer.Target] = [buffer.Target, buffer.PingPongTarget];
                buffer.Shader.uniforms[`iChannel${buffer.PingPongChannel}`].value = buffer.PingPongTarget.texture;
                for (let dependent of buffer.Dependents) {
                    const dependentBuffer = buffers[dependent.Index];
                    dependentBuffer.Shader.uniforms[`iChannel${dependent.Channel}`].value = buffer.Target.texture;
                }
            }
        }

        frameCounter++;
    }
    function forceAspectRatio(width, height) {
        // Forced aspect ratio
        let forcedAspects = [0,0];
        let forcedAspectRatio = forcedAspects[0] / forcedAspects[1];
        let aspectRatio = width / height;

        if (forcedAspectRatio <= 0 || !isFinite(forcedAspectRatio)) {
            let resolution = new THREE.Vector3(width, height, 1.0);
            return resolution;
        }
        else if (aspectRatio < forcedAspectRatio) {
            let resolution = new THREE.Vector3(width, Math.floor(width / forcedAspectRatio), 1);
            return resolution;
        }
        else {
            let resolution = new THREE.Vector3(Math.floor(height * forcedAspectRatio), height, 1);
            return resolution;
        }
    }
    function computeSize() {
        
        // Compute forced aspect ratio and align canvas
        resolution = forceAspectRatio(window.innerWidth, window.innerHeight);
        canvas.style.left = `${(window.innerWidth - resolution.x) / 2}px`;
        canvas.style.top = `${(window.innerHeight - resolution.y) / 2}px`;

        for (let buffer of buffers) {
            if (buffer.Target) {
                buffer.Target.setSize(resolution.x, resolution.y);
            }
            if (buffer.PingPongTarget) {
                buffer.PingPongTarget.setSize(resolution.x, resolution.y);
            }
        }
        renderer.setSize(resolution.x, resolution.y, false);
        
        // Update Camera and Mesh
        quad.geometry = new THREE.PlaneGeometry(resolution.x, resolution.y);
        camera.left = -resolution.x / 2.0;
        camera.right = resolution.x / 2.0;
        camera.top = resolution.y / 2.0;
        camera.bottom = -resolution.y / 2.0;
        camera.updateProjectionMatrix();

        // Reset iFrame on resize for shaders that rely on first-frame setups
        frameCounter = 0;
    }
    function saveScreenshot() {
        let doSaveScreenshot = () => {
            renderer.domElement.toBlob(function(blob){
                let a = document.createElement('a');
                let url = URL.createObjectURL(blob);
                a.href = url;
                a.download = 'shadertoy.png';
                a.click();
            }, 'image/png', 1.0);
        };

        let forcedScreenshotResolution = [0,0];
        if (forcedScreenshotResolution[0] <= 0 || forcedScreenshotResolution[1] <= 0) {
            renderer.render(scene, camera);
            doSaveScreenshot();
        }
        else {
            renderer.setSize(forcedScreenshotResolution[0], forcedScreenshotResolution[1], false);
            
            for (let buffer of buffers) {
                buffer.Shader.uniforms['iResolution'].value = new THREE.Vector3(forcedScreenshotResolution[0], forcedScreenshotResolution[1], 1);
                buffer.Shader.uniforms['resolution'].value = new THREE.Vector3(forcedScreenshotResolution[0], forcedScreenshotResolution[1], 1);

                quad.material = buffer.Shader;
                renderer.setRenderTarget(buffer.Target);
                renderer.render(scene, camera);
            }

            doSaveScreenshot();
            renderer.setSize(resolution.x, resolution.y, false);
        }
    }
    function recordAction() {
        let recordButton = document.getElementById("record");
        if (recorder == null) {
            recordButton.classList.add('recording');

            let stream = canvas.captureStream(30);
            let recorderOptions = {
                mimeType: "video/webm"
            };
            recorder = new MediaRecorder(stream, recorderOptions);
            recorder.start();
            recorder.ondataavailable = function(evt) {
                let a = document.createElement('a');
                let url = URL.createObjectURL(evt.data);
                a.href = url;
                a.download = 'shadertoy.webm';
                a.click();
            };
        }
        else {
            recordButton.classList.remove('recording');

            recorder.stop();
            recorder = null;
        }
    }
    function reloadWebview() {
        if (vscode !== undefined) {
            vscode.postMessage({ command: 'reloadWebview' });
        }
    }
    function updateMouse() {
        if (vscode !== undefined) {
            vscode.postMessage({
                command: 'updateMouse',
                mouse: {
                    x: mouse.x,
                    y: mouse.y,
                    z: mouse.z,
                    w: mouse.w
                },
                normalizedMouse: {
                    x: normalizedMouse.x,
                    y: normalizedMouse.y
                }
            });
        }
    }
    let dragging = false;
    function updateNormalizedMouseCoordinates(clientX, clientY) {
        let rect = canvas.getBoundingClientRect();
        let mouseX = clientX - rect.left;
        let mouseY = resolution.y - clientY - rect.top;

        if (mouseButton.x + mouseButton.y != 0) {
            mouse.x = mouseX;
            mouse.y = mouseY;
        }

        normalizedMouse.x = mouseX / resolution.x;
        normalizedMouse.y = mouseY / resolution.y;
    }
    canvas.addEventListener('mousemove', function(evt) {
        updateNormalizedMouseCoordinates(evt.clientX, evt.clientY);
        updateMouse();
    }, false);
    canvas.addEventListener('mousedown', function(evt) {
        if (evt.button == 0)
            mouseButton.x = 1;
        if (evt.button == 2)
            mouseButton.y = 1;

        if (!dragging) {
            updateNormalizedMouseCoordinates(evt.clientX, evt.clientY);
            mouse.z = mouse.x;
            mouse.w = mouse.y;
            dragging = true
        }

        updateMouse();
    }, false);
    canvas.addEventListener('mouseup', function(evt) {
        if (evt.button == 0)
            mouseButton.x = 0;
        if (evt.button == 2)
            mouseButton.y = 0;

        dragging = false;
        mouse.z = -mouse.z;
        mouse.w = -mouse.w;

        updateMouse();
    }, false);
    window.addEventListener('resize', function() {
        computeSize();
    });

    // Keyboard Callbacks
</script>